﻿using System;
using System.Collections.Generic;
using System.IO;
using PSE.Updater.Common;
using PSE.Updater.Common.Configurations.Server;
using PSE.Updater.Common.Providers;
using PSE.Updater.Server.Engine.Exceptions;
using PSE.Updater.Common.Providers.Interfaces;
using PSE.Updater.Server.Engine.Providers;
using PSE.Framework.ErrorLogging;

namespace PSE.Updater.Server.Engine
{
    /// <summary>
    /// Classe para manipular a monitoração de pastas
    /// </summary>
    public class Watcher
    {
        public class Options
        {
            public string Directory { get; set; }
            public string ExtensionsFilter { get; set; }
        }

        private Options _options;

        public ProviderType ProviderType { get; private set; }
        
        private CacheManager _cacheManager;
        private FileSystemWatcher _directoryWatcher;

        /// <summary>
        /// Inicia a Monitoração de pasta
        /// </summary>
        /// <param name="watcher">Informações de configuração do Watcher</param>
        /// <param name="provider">Objeto de Provider contendo as informações de objetos a serem usados</param>
        /// <param name="productName">Nome do Produto</param>
        public void Start(Options options, ProviderType type)
        {
            _cacheManager = CacheManager.Instance;
            
            _options = options;

            if (!string.IsNullOrEmpty(_options.Directory))
            {
                if (!System.IO.Directory.Exists(_options.Directory))
                    System.IO.Directory.CreateDirectory(_options.Directory);
            }

            this.ProviderType = type;
            
            _directoryWatcher = new FileSystemWatcher(_options.Directory, _options.ExtensionsFilter);
            _directoryWatcher.IncludeSubdirectories = true;
            _directoryWatcher.Created += (sender, e) => { Created(e.FullPath); };
            _directoryWatcher.Renamed += (sender, e) => { Renamed(e.FullPath, e.OldFullPath); };
            _directoryWatcher.Deleted += (sender, e) => { Deleted(e.FullPath); };
            _directoryWatcher.EnableRaisingEvents = true;
            _directoryWatcher.Error += (sender, e) => { OnError(e.GetException()); };

            RetrieveAndCachePackages(_options.Directory);
        }

        /// <summary>
        /// Método disparado quando um Arquivo é excluído de uma pasta
        /// </summary>
        /// <param name="item">Arquivo Deletado</param>
        private void Deleted(string item)
        {
            try
            {
                DebugLog.Write("Método disparado quando um Arquivo é excluído de uma pasta. " + item);

                //se um diretorio for excluido, refaz o cache. Isso é necessário pois no caso da exclusao de um diretorio, 
                //nao sao disparados eventos de delete para os arquivos contidos no diretorio, impossibilitando um controle mais fino
                if (IsDirectory(item))
                {
                    RetrieveAndCachePackages(_options.Directory);
                }
                else
                {
                    _cacheManager.RemoveFile(item);
                }
            }
            catch (InvalidPackageException ex)
            {
                //n parar a execucao por n se tratar de um erro critico
                ErrorLogger.WriteLog(LogType.Error, ex);
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
                //throw ex;
            }
        }

        /// <summary>
        /// Método usado quando um arquivo é renomeado
        /// </summary>
        /// <param name="item">Novo nome do Arquivo</param>
        /// <param name="oldItem">Nome antigo do Arquivo</param>
        private void Renamed(string item, string oldItem)
        {
            try
            {
                DebugLog.Write("Método usado quando um arquivo é renomeado: " + oldItem + " para " + item);

                //se um diretorio for renomeado/movido, refaz o cache. Isso é necessário pois, nesse caso, 
                //nao sao disparados eventos de rename para os arquivos contidos no diretorio, impossibilitando um controle mais fino
                if (IsDirectory(item))
                {
                    RetrieveAndCachePackages(_options.Directory);
                }
                else
                {
                    IFileDetails details = ProviderFactory.Instance.CreateFileDetails(this.ProviderType, string.Empty, item, _options.Directory);
                    _cacheManager.RemoveFile(oldItem);
                    _cacheManager.AddOrUpdate(details);
                }
            }
            catch (InvalidPackageException ex)
            {
                //n parar a execucao por n se tratar de um erro critico
                ErrorLogger.WriteLog(LogType.Error, ex);
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
                //throw ex;
            }
        }

        /// <summary>
        /// Método disparado quando um novo arquivo é incluido numa pasta
        /// </summary>
        /// <param name="item">Caminho do Novo arquivo</param>
        private void Created(string item)
        {
            try
            {
                DebugLog.Write("Método disparado quando um novo arquivo é incluido numa pasta: " + item);

                //se um diretorio for criado, refaz o cache. Isso é necessário pois, nesse caso, 
                //nao sao disparados eventos de create para os arquivos contidos no diretorio, impossibilitando um controle mais fino
                if (IsDirectory(item))
                {
                    RetrieveAndCachePackages(_options.Directory);
                }
                else
                {
                    IFileDetails details = ProviderFactory.Instance.CreateFileDetails(this.ProviderType, string.Empty, item, _options.Directory);
                    _cacheManager.AddOrUpdate(details);
                }
            }
            catch (InvalidPackageException ex)
            {
                //n parar a execucao por n se tratar de um erro critico
                ErrorLogger.WriteLog(LogType.Error, ex);
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
                //throw ex;
            }
        }

        /// <summary>
        /// Método que inclui no cache os arquivos disponíveis numa determinada pasta
        /// </summary>
        /// <param name="item">Caminho da pasta contendo arquivos</param>
        private void RetrieveAndCachePackages(string item)
        {
            DebugLog.Write("Obtendo os arquivos da pasta " + item);
            string[] files = Directory.GetFiles(item, _options.ExtensionsFilter, SearchOption.AllDirectories);

            if (files != null && files.Length > 0)
            {
                foreach (string file in files)
                {
                    try
                    {
                        IFileDetails details = ProviderFactory.Instance.CreateFileDetails(this.ProviderType, string.Empty, file, _options.Directory);
                        DebugLog.Write("Cacheando o pacote " + details.FilePath);
                        _cacheManager.AddOrUpdate(details);
                    }
                    catch (InvalidPackageException ex)
                    {
                        ErrorLogger.WriteLog(LogType.Error, ex);
                    }
                }
            }
        }

        /// <summary>
        /// Método disparado quando ocorre uma determinada exceção
        /// </summary>
        /// <param name="ex">Exceção ocorrida</param>
        private void OnError(Exception ex)
        {
            ErrorLogger.WriteLog(LogType.Error, ex);
            //throw ex;
        }

        /// <summary>
        ///  Pára o serviço de Monitoração
        /// </summary>
        public void Stop()
        {
            try
            {
                if (_directoryWatcher != null)
                    _directoryWatcher.Dispose();

                _cacheManager.Clear();
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
                //throw ex;
            }
        }

        /// <summary>
        /// Verifica se um determinado caminho é para um diretório
        /// </summary>
        /// <param name="path">Caminho a ser verificado</param>
        /// <returns>True/False Caso seja um diretório ou não</returns>
        private bool IsDirectory(string path)
        {
            if (File.Exists(path))
                return false;
            else 
                return string.IsNullOrEmpty(Path.GetExtension(path));
        }

        /// <summary>
        /// Retorna uma lista de arquivos disponibilizados no cache
        /// </summary>
        /// <returns>Uma lista de arquivos disponibilizados no cache</returns>
        public IList<IFileDetails> ListFileDetails()
        {
            return _cacheManager.List(this.ProviderType);
        }

        /// <summary>
        /// Retorna uma lista de arquivos disponibilizados no cache por produto
        /// </summary>
        /// <returns>Uma lista de arquivos disponibilizados no cache por produto</returns>
        public IList<IFileDetails> ListFileDetails(string productname)
        {
            return _cacheManager.List(this.ProviderType, productname);
        }

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            this.Stop();
        }
    }
}
